<?php
require_once('HttpInputFilter.class.php');
require_once('Utils.class.php');
define('AUTH_ERR_NOSUCHUSER', 2);			//Fail to authenticate because there's no such user
define('AUTH_ERR_WRONGPWD', 3);				//Fail to authenticate becaues the specified user and password do not match
define('AUTH_ERR_DISABLED', 4);				//Fail to authenticate because the user is set disabled
define('CHANGEPWD_ERR_AUTHFAILURE', 5);		//Fail to change password because the authentication failed.
define('CHANGEPWD_ERR_UPDATEFAILURE', 6);	//Fail to change password because an error occurred while updating
class Authentication
{
	private static  $_loginTable = TB_USER;//the table name contain the login infomation
	private static  $_TableField = array(
		'userId'=> 'id', 
		'loginName' => 'username',
		'password'  => 'password',
		
	);  
	private static  $_rememberTime = 2592000;//one month
	private static  $_loginUrl = SYS_LOGIN_PAGE;
	private static  $_homeUrl = SYS_LOGIN_HOME;
	private static  $renewExpireTime = false;
	public  function __construct(){}
	
	/**
	 * check if the provided username and password math
	 * bool checkUserPassword(string $user , string $password)
	 * @param  string $user ,the case insensitive username, starting and ending spaces will be trimmed
	 * @param  string $password, 
	 * @return bool true if password and username match else false 
	 */
	public static function checkUserPassword($user,$password)
	{
		if(!$user || !$password) return false;
		$user = strtolower(trim($user));
		$loginTable  = self::$_loginTable;
		$userIdField = self::$_TableField['userId'];
		$loginNameFiled = self::$_TableField['loginName'];
		$passwordField = self::$_TableField['password'];
	
		$db = getDb(true);
		$sql = "select $loginNameFiled,$passwordField from $loginTable where $loginNameFiled = {0}";
		
		$re = $db->fetchOne($sql,$user);
		if(!$re) return  false;
		if(strcasecmp(trim($re[$passwordField]), self::encryptPassword($password)) == 0)
			return true;
		else
			return false;
		
	}
	 /**
	 * Get encypted password
	 * string encryptPassword(string $salt, string $pwd)
	 * @return		the encypted password
	 *
	 */
	private static function encryptPassword($pwd) {
		return md5($pwd);
	}
	/**
	 * Connects to a session and checks that the user has
	 * authenticated and that the remote IP address matches
	 * the address used to create the session.
	 * This function also renews login expire time according to config
	 * bool sessionAuthenticate()
	 * @return		bool true if authenticated otherwise false
	 * 				if false is returned $_SESSION['auth']["message"] is set
	 *
	 */
	//ONE THING TO SAY: $_SESSION['auth']['ok'] === true is the only thing to prove that the user has logged in!

		private static function sessionAuthenticate() {
		if (!isset($_SESSION)) session_start();
		// Check if the user has logged in
		if (!isset($_SESSION['auth']['ok']) || $_SESSION['auth']['ok'] !== true)
		{
			// The request does not identify a session
			$_SESSION['auth']["message"] = "You are not authorized to access the URL
	                            {$_SERVER["REQUEST_URI"]}";
			return false;
		}
		// Check if the request is from a different IP address to previously
		if (false && !isset($_SESSION['auth']["loginIP"]) ||
		($_SESSION['auth']["loginIP"] != $_SERVER["REMOTE_ADDR"]))
		{
			// The request did not originate from the machine
			// that was used to create the session.
			// THIS IS POSSIBLY A SESSION HIJACK ATTEMPT
			$_SESSION['auth']["message"] = "You are not authorized to access the URL
	                            {$_SERVER["REQUEST_URI"]} from the address 
	                            {$_SERVER["REMOTE_ADDR"]}";
			return false;
		}
		if($_SESSION['auth']["expireTime"] < time()) {
			$_SESSION['auth']["message"] = "Login Time out";
			Authentication::_onLoginTimeOut();
			return false;
		}
		if(self::$renewExpireTime === true) {
			$_SESSION['auth']['expireTime'] = time() + self::$_rememberTime;
		}
		return true;
	}
	/**
	 * Authorizes a user. Mark as authenticated by store user info in session.
	 *
	 * @param string $loginName
	 * @return bool
	 */
	private static function authorize($loginName) {
		if (!isset($_SESSION)) session_start();
		$loginTable  = self::$_loginTable;
		$userIdField = self::$_TableField['userId'];
		$loginNameFiled = self::$_TableField['loginName'];
		$passwordField = self::$_TableField['password'];
		$db = getDb(true);
		$sql = "select $userIdField ,$loginNameFiled from $loginTable where $loginNameFiled = {0}";
		$authInfo = $db->fetchOne($sql,$loginName);
		if (!$authInfo) return false;
		
		unset($_SESSION['auth']);
		$_SESSION['auth']['userId'] = $authInfo[$userIdField];
		$_SESSION['auth']['loginName'] = $authInfo[$loginNameFiled];
		$_SESSION['auth']['expireTime'] = time() + self::$_rememberTime;
		$_SESSION['auth']['loginIP'] = $_SERVER['REMOTE_ADDR'];
		$_SESSION['auth']['ok'] = true;
		$_SESSION['auth']['message'] = 'successful';
		
		unset($_SESSION['userInfo']);
		
		$_SESSION['userInfo']['userId'] = $authInfo[$userIdField];
		$_SESSION['userInfo']['loginName'] = $authInfo[$loginNameFiled];
		
		return true;
	}

	/**
	 * unauthorize the current user(logout)
	 * void unauthorize()
	 * here I simply unset the $_SESSION['auth']
	 *
	 */
	private static function unauthorize() {
		if (!isset($_SESSION)) session_start();
		$_SESSION['auth']['expireTime'] = time() - 86400;
		unset($_SESSION['auth']);
		unset($_SESSION['userInfo']);
	}
	/**
	 * handle login timeout
	 * void _onLoginTimeOut()
	 * used internally
	 *
	 */
	private static function _onLoginTimeOut() {
		self::unauthorize();
	}
	/**
	 * logs in a user, authorize if successful
	 * @return bool
	 */
	public static function login($user, $password, $rememberInCookie = false)
	{
		if (!(Authentication::checkUserPassword($user, $password) === true)) {
			return false;
		}
		self::authorize($user);
        
        //count login activity
        $db = getDb();
        $sql = 'update ' . self::$_loginTable . ' set last_login=NOW() where username={0}';
        $db->query($sql, array(trim($user)));
        	
		if ($rememberInCookie) {
			self::rememberInCookie($user);	
		} else {
			self::forgetInCookie($user);
		}
		return true;
	}
	
	
	public static function logoff()
	{
		if(isset($_SESSION['userInfo']['userId']))
			$userId = (int)$_SESSION['userInfo']['userId'];
		else 
			$userId = 9999999999;
		//Even if session authentication results false, there is work to do
		self::unauthorize();

		if (isset($_SESSION['userInfo']['loginName'])) {
			$loginName = $_SESSION['userInfo']['loginName'];
		} else {
			$loginName = 'anonymous';
		}
		self::forgetInCookie($loginName);
	}
	
	/**
	 * Gets current user info, if not logged in, false is returned
	 * This method is designed to be called after calling checkLogin or requireLogin
	 *
	 * @return array|NULL
	 */
	public static function currentUser($key=NULL)
	{
		//TODO: remove this
		if (!self::authenticate()) {
			return NULL;
		}
		
		if (isset($_SESSION['userInfo'])) {
			if ($key === NULL)
			return $_SESSION['userInfo'];
			else
			return $_SESSION['userInfo'][$key];
		} else
		return NULL;
	}
	
	/**
	 * Generates a 32 chars unique string
	 *
	 * @return string
	 */
	private static function generateCookieIdendificationCode()
	{
		mt_srand((double)microtime()*10000);//optional for php 4.2.0 and up.
		return strtoupper(md5(uniqid(rand(), true)));
	}
	
	/**
	 * Logs a user in by cookie
	 *
	 * @return bool
	 */
	private static function cookieLogin() {
		$h = new HttpInputFilter($_COOKIE);
		$user = $h->getNoneEmpty('username');
		if(!empty($user)){
			if (self::authorize($user)) {
				return true;
			}
			return false;
		
		}
		return false;
		
	}
	/**
	 * Checks whether current user has logged in. First try session, then cookie
	 *
	 * @return bool
	 */
	public static function authenticate() {
		if (self::sessionAuthenticate()) {
			return true;
		} else {
			return self::cookieLogin();
		}
	}
	
	private static function rememberInCookie($user) {
		$code = self::generateCookieIdendificationCode();

		$expire = time() + self::$_rememberTime;
		setcookie('username', $user, $expire, '/');
		setcookie('IDCode', $code, $expire, '/');
	}
	private static function forgetInCookie($user=NULL) {

		$expire = time() - 60*60*24*365*1;
		setcookie('username', '', $expire, '/');
		setcookie('IDCode', '', $expire, '/');
	}
	
	/**
	 * Tells whether the current user has logged in.
	 * This is the only appropriate method used to check login status
	 *
	 * @return bool
	 */
	public static function checkLogin() {
		if (self::authenticate()) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Ensures that the user has logged in.
	 * If current user has not logged in, this will redirect to the loggin page
	 *
	 */
	public static function requireLogin() {
		if (!self::checkLogin()) {
//			$url = Utils::buildUrl(self::$_loginUrl, array('redirectUrl' => Utils::getRequestURL()));
			//$url = self::$_loginUrl.'/'.http_build_query(array('redirectUrl' => Utils::getRequestURL()));
            $data = base64_encode(Utils::getRequestURL());
            echo $data;
            Utils::redirect(self::$_loginUrl . "/$data", true);
		}	
	}
}